package com.enba.boot.payment.alipay;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.factory.Factory.Payment;
import com.alipay.easysdk.kernel.Config;
import com.alipay.easysdk.payment.common.models.AlipayTradeFastpayRefundQueryResponse;
import com.alipay.easysdk.payment.common.models.AlipayTradeQueryResponse;
import com.alipay.easysdk.payment.common.models.AlipayTradeRefundResponse;
import com.alipay.easysdk.payment.facetoface.models.AlipayTradePrecreateResponse;
import com.alipay.v3.ApiException;
import com.alipay.v3.util.AlipaySignature;
import com.enba.boot.payment.base.PayProductBase;
import com.enba.boot.payment.base.RefundProductBase;
import com.enba.boot.payment.properties.AliPayProperties;
import com.google.common.collect.Maps;
import java.math.BigDecimal;
import java.util.Map;
import java.util.function.Function;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

/** 支付宝支付Helper */
@Slf4j
public class AliPayHelper {

  private static final Logger logger = LoggerFactory.getLogger(AliPayHelper.class);

  private final AliPayProperties aliPayProperties;

  public AliPayHelper(AliPayProperties aliPayProperties) {
    this.aliPayProperties = aliPayProperties;

    Config config = new Config();
    config.protocol = "https";
    config.gatewayHost = aliPayProperties.getGateWayHost();
    config.signType = "RSA2";
    config.appId = aliPayProperties.getAppId();
    config.merchantPrivateKey = aliPayProperties.getMerchantPrivateKey();
    config.notifyUrl = aliPayProperties.getNotifyUrl();
    config.encryptKey = aliPayProperties.getEncryptKey();
    // 证书模式
    if (aliPayProperties.getMode()) {
      config.merchantCertPath = aliPayProperties.getMerchantCertPath();
      config.alipayCertPath = aliPayProperties.getAlipayCertPath();
      config.alipayRootCertPath = aliPayProperties.getAlipayRootCertPath();
    }
    // 密钥模式
    else {
      config.alipayPublicKey = aliPayProperties.getAliPayPublicKey();
    }
    Factory.setOptions(config);
  }

  /** 检查支付宝配置信息 */
  public void checkAliPayProperties() {
    logger.info(
        "支付宝配置信息：{}",
        JSON.toJSONString(aliPayProperties, SerializerFeature.WRITE_MAP_NULL_FEATURES));

    String gateWayHost = aliPayProperties.getGateWayHost();
    String appId = aliPayProperties.getAppId();
    String merchantPrivateKey = aliPayProperties.getMerchantPrivateKey();
    String notifyUrl = aliPayProperties.getNotifyUrl();
    String encryptKey = aliPayProperties.getEncryptKey();

    if (!StringUtils.hasText(gateWayHost)
        || !StringUtils.hasText(appId)
        || !StringUtils.hasText(merchantPrivateKey)
        || !StringUtils.hasText(notifyUrl)
        || !StringUtils.hasText(encryptKey)) {
      throw new RuntimeException("请检查支付宝支付配置信息");
    }

    // 证书模式
    if (aliPayProperties.getMode()) {
      String appCertPath = aliPayProperties.getMerchantCertPath();
      String alipayCertPath = aliPayProperties.getAlipayCertPath();
      String alipayRootCertPath = aliPayProperties.getAlipayRootCertPath();

      if (!StringUtils.hasText(appCertPath)
          || !StringUtils.hasText(alipayCertPath)
          || !StringUtils.hasText(alipayRootCertPath)) {
        throw new RuntimeException("请检查支付宝支付配置信息");
      }
    }
    // 密钥模式
    else {
      String aliPayPublicKey = aliPayProperties.getAliPayPublicKey();

      if (!StringUtils.hasText(aliPayPublicKey)) {
        throw new RuntimeException("请检查支付宝支付配置信息");
      }
    }
  }

  /**
   * 当面付-生成交易付款码，待用户扫码付款
   *
   * @param productBase 支付参数
   * @return 支付宝返回结果
   * @throws Exception 支付宝异常
   */
  public AlipayTradePrecreateResponse faceToFace(PayProductBase productBase) throws Exception {
    String notifyUrl = productBase.getNotifyUrl();
    if (StringUtils.hasText(notifyUrl)) {
      return Payment.FaceToFace()
          .asyncNotify(productBase.getNotifyUrl())
          .preCreate(
              productBase.getSubject(),
              productBase.getOutTradeNo(),
              String.valueOf(
                  new BigDecimal(productBase.getTotalAmount()).divide(new BigDecimal("100"))));
    }

    return Payment.FaceToFace()
        .preCreate(
            productBase.getSubject(),
            productBase.getOutTradeNo(),
            String.valueOf(
                new BigDecimal(productBase.getTotalAmount()).divide(new BigDecimal("100"))));
  }

  /**
   * 交易查询
   *
   * @param outTradeNo 商户订单号
   * @return r
   * @throws Exception e
   */
  public AlipayTradeQueryResponse query(String outTradeNo) throws Exception {
    return Factory.Payment.Common().query(outTradeNo);
  }

  /**
   * 交易退款(当交易发生之后一段时间内，由于买家或者卖家的原因需要退款时，卖家可以通过退款接口将支付款退还给买家，支付宝将在收到退款请求并且验证成功之后，按照退款规则将支付款按原路退到买家帐号上。
   * 交易超过约定时间（签约时设置的可退款时间）的订单无法进行退款。
   * 支付宝退款支持单笔交易分多次退款，多次退款需要提交原支付订单的订单号和设置不同的退款请求号。一笔退款失败后重新提交，要保证重试时退款请求号不能变更，防止该笔交易重复退款。
   * 同一笔交易累计提交的退款金额不能超过原始交易总金额。)
   *
   * @return r
   * @throws Exception e
   */
  public AlipayTradeRefundResponse refund(RefundProductBase refundProductBase) throws Exception {
    // 1.部分退款时：检查是否设置out_request_no参数，该参数是标识一次退款请求，同一笔交易多次退款需要保证唯一，且 部分退款，则此参数必传。
    // 2.全部退款：检查该笔交易的支付金额与退款金额是否一致；
    return Factory.Payment.Common()
        .optional("out_request_no", refundProductBase.getOutRefundNo())
        .optional("refund_reason", refundProductBase.getRefundReason())
        .refund(
            refundProductBase.getOutTradeNo(),
            String.valueOf(
                new BigDecimal(refundProductBase.getRefundAmount()).divide(new BigDecimal("100"))));
  }

  /**
   * 退款查询
   *
   * @param refundProductBase refundProductBase
   * @return r
   * @throws Exception e
   */
  public AlipayTradeFastpayRefundQueryResponse refundQuery(RefundProductBase refundProductBase)
      throws Exception {
    return Factory.Payment.Common()
        .queryRefund(refundProductBase.getOutTradeNo(), refundProductBase.getOutRefundNo());
  }

  /**
   * 支付宝回调
   *
   * @param request 请求
   * @param func 自定义回调逻辑
   * @param <R> 回调函数返回值类型
   * @return
   * @throws ApiException
   */
  public <R> R notifyCallback(HttpServletRequest request, Function<Map<String, String>, R> func)
      throws ApiException {
    Map<String, String> parameters = Maps.newHashMap();
    if (request.getParameter("trade_status").equals("TRADE_SUCCESS")) {
      Map<String, String[]> requestParams = request.getParameterMap();
      for (String name : requestParams.keySet()) {
        parameters.put(name, request.getParameter(name));
      }

      log.info("parameters###{}", parameters);

      boolean pass =
          AlipaySignature.certVerifyV1(parameters, "alipayPublicCert.crt", "utf-8", "RSA2");

      // 验签
      if (pass) {
        log.info("验签成功");
        log.info("通知时间。通知的发送时间。格式为 yyyy-MM-dd HH:mm:ss。###{}", parameters.get("notify_time"));
        log.info("通知类型###{}", parameters.get("notify_type"));
        log.info("通知校验 ID###{}", parameters.get("notify_id"));
        log.info("签名类型###{}", parameters.get("sign_type"));
        log.info("签名###{}", parameters.get("sign"));
        log.info("支付宝交易号。支付宝交易凭证号。###{}", parameters.get("trade_no"));
        log.info("开发者的 app_id###{}", parameters.get("app_id"));
        log.info("商户订单号###{}", parameters.get("out_trade_no"));
        log.info("商家业务号。商家业务 ID，主要是退款通知中返回退款申请的流水号。###{}", parameters.get("out_biz_no"));
        log.info("买家支付宝用户号###{}", parameters.get("buyer_id"));
        log.info("买家支付宝账号###{}", parameters.get("buyer_logon_id"));
        log.info("卖家支付宝用户号###{}", parameters.get("seller_id"));
        log.info("卖家支付宝账号###{}", parameters.get("seller_email"));
        log.info("交易状态###{}", parameters.get("trade_status"));
        log.info("订单金额。本次交易支付的订单金额，单位为人民币（元）。支持小数点后两位。###{}", parameters.get("total_amount"));
        log.info("实收金额。商家在交易中实际收到的款项，单位为人民币（元）。支持小数点后两位。###{}", parameters.get("receipt_amount"));
      } else {
        log.error("验签失败");
      }
    } else {
      log.error("支付失败");
    }

    return func.apply(parameters);
  }
}
